import {
  Cartesian3,
  ClockRange,
  ClockStep,
  defined,
  JulianDate,
  TimeIntervalCollection,
  Matrix4,
  Math as CesiumMath,
  Resource,
  SampledPositionProperty,
  TimeInterval,
  VelocityOrientationProperty,
  Color,
  PolylineGlowMaterialProperty,
  LagrangePolynomialApproximation, Cartographic, HeadingPitchRange, SampledProperty, TimeStandard, Transforms, Matrix3
} from "cesium";

/**
 *  cesium漫游功能实现
 */
  export class CesiumRoaming {
  /**
   * Creates an instance of Roaming.
   *  @param {*} options.data  漫游经过的坐标集合
   * @param {*} options.model 模型
   * @param {*} options.time 漫游所需要的时间
   * @param {*} options.speed 速率
   * @param {*} options.isPathShow 路径是否显示 默认不显示
   * @param options
   */
  constructor(options) {
    this.entity = undefined;
    this.start = undefined;
    this.stop = undefined;
    this.isPathShow = true
    // 场景事件
    this.handler = null
  }

  /**
   * 初始化漫游
   * @param options
   * @returns {Promise<void>}
   */
  async initRoaming(options) {
    // 移除漫游
    this.removeRoaming()
    // 根据最配置更新内部参数
    this.updateOptionsParams(options)
    this.createRoaming(this.data)
    // // 获取数据
    // const result = await this.loadData(this.data)
    // if (result) {
    //   // 路径数据
    //   const data = result.path
    //   //创建漫游
    //   this.createRoaming(data)
    // }
  }

  /**
   * 创建漫游
   * @param data
   */
  createRoaming(data) {
    if (data) {
      // 坐标转换
      const positions = this.processData(data)
      // //根据路径数据生成漫游路线
      this.property = this.roamingLine(positions, this.time)
      console.log(this.property, 'this.property')
      // // 创建线实例
      this.createEntity(positions, this.start, this.stop, this.isPathShow);

    }
  }

  /**
   * 创建位置的集合，将时间和位置绑定在一起
   * @param coordinates
   * @param time.
   * @returns {module:cesium.SampledPositionProperty}
   */
  roamingLine(coordinates, time) {
    const property = new SampledPositionProperty()
    const coordinatesLength = coordinates.length;
    const tempTime = time - (time % coordinatesLength);
    //间隔时间
    const increment = tempTime / coordinatesLength;
    const start = JulianDate.now()
    const stop = JulianDate.addSeconds(start, tempTime, new JulianDate())
    this.start = start;
    this.stop = stop
    this.setClockTime(start, stop, this.multiplier)
    for (let i = 0; i < coordinatesLength; i++) {
      // 将提供的秒数添加到提供的日期实例中
      const timePerPosition = JulianDate.addSeconds(start, i * increment, new JulianDate())
      const position = coordinates[i]
      // 添加新样本
      property.addSample(timePerPosition, position)
    }
    return property
  }


  /**
   * 设置漫游事件
   */
  setClockTime() {
    //将当前时间转为JulianDate
    window.viewer.clock.startTime = this.start.clone()
    window.viewer.clock.stopTime = this.stop.clone()
    window.viewer.clock.currentTime = this.start.clone()
    window.viewer.clock.multiplier = this.multiplier;
    window.viewer.clock.shouldAnimate = true; // 这将启动时间的流动
    //默认漫游结束后停止
    window.viewer.clock.clockStep = ClockStep.SYSTEM_CLOCK_MULTIPLIER
    window.viewer.clock.clockRange = ClockRange.LOOP_STOP; // 漫游结束后停止
  }


  /**
   * 创建entity
   * @param position 坐标
   * @param start 开始时间
   * @param stop 结束时间
   * @param isPathShow 路径是否显示
   */
  createEntity(position, start, stop, isPathShow) {
    this.entity = window.viewer.entities.add({
      availability: new TimeIntervalCollection([new TimeInterval({
        start, stop
      })]),
      //位置
      position: this.property,
      // //计算朝向
      orientation: new VelocityOrientationProperty(this.property),
      // //加载模型
      model: {
        uri: new URL('../assets/Cesium_Air.glb', import.meta.url).href,
        // 模型最小刻度
        minimumPixelSize: 20,
        // 设置模型最大放大大小
        maximumScale: 20,
        // 模型是否可见
        show: true,
        // 模型轮廓颜色
        silhouetteColor: Color.WHITE,
        // 模型颜色  ，这里可以设置颜色的变化
        // color: color,
        // 仅用于调试，显示模型绘制时的线框
        debugWireframe: false,
        // 仅用于调试。显示模型绘制时的边界球。
        debugShowBoundingVolume: false,
        scale: 2,
        runAnimations: true, // 是否运行模型中的动画效果
      },
      path: {
        resolution: 1,
        material: new PolylineGlowMaterialProperty({
          glowPower: 0.1,
          color: Color.BLACK,
        }),
        width: 10,
        show: isPathShow,
      },
    })
    this.property.setInterpolationOptions({
      interpolationDegree: 5,
      interpolationAlgorithm: LagrangePolynomialApproximation,
    })
    // 可以设置相机跟随实体
    // window.viewer.trackedEntity = this.entity;
    this.addSceneEvent((event, time) => {
      this.getRoamingPosition(time)
    })
  }

  /**
   * 设置相机的位置
   * @param position
   * @param options
   */
  setCameraPosition(position, options) {
    if (position) {
      //最新传进来的坐标（后一个位置）
      this.position2 = this.cartesian3ToWGS84(position)
      let heading = 0;
      //前一个位置点位
      if (this.position1) {
        //计算前一个点位与第二个点位的偏航角
        heading = this.bearing(this.position1.latitude, this.position1.longitude, this.position2.latitude, this.position2.longitude)
      }
      this.position1 = this.cartesian3ToWGS84(position)
      if (position) {
        let center = this.entity.position.getValue(
            window.viewer.clock.currentTime
        );
        let orientation = this.entity.orientation.getValue(
            window.viewer.clock.currentTime
        )
        // 第一人称跟随
        let transform = Transforms.eastNorthUpToFixedFrame(center);
        transform = Matrix4.fromRotationTranslation(Matrix3.fromQuaternion(orientation), center);
        window.viewer.camera.lookAtTransform(transform, new Cartesian3(-100, 0, 50))


        // const dynamicHeading = CesiumMath.toRadians(heading)
        // const pitch = CesiumMath.toRadians(options.pitch || -20.0)
        // const range =  options.range || 2000.0
        // window.viewer.camera.lookAt(position, new HeadingPitchRange(dynamicHeading, pitch, range))
      }
    }
  }


  /**
   * 数据处理
   * @param data {longitude, latitude, height}
   * @returns {[longitude, latitude, height]}
   */
  processData(data) {
    const coordinates = [];
    for (let i = 0; i < data.length; i++) {
      const position = Cartesian3.fromDegrees(data[i].longitude, data[i].latitude, data[i].height)
      coordinates.push(position)
    }
    return coordinates
  }

  /**
   *  坐标转换
   * @param point radius下的WGS84坐标
   * @returns {{latitude: number, longitude: number, height: number}} 弧度下的WGS84坐标
   */
  cartesian3ToWGS84(point) {
    // 从笛卡尔位置创建一个新的制图实例。中的值结果对象将以弧度表示
    const cartographic = Cartographic.fromCartesian(point)
    let lat, lng, alt
    lat = CesiumMath.toDegrees(cartographic.latitude)
    lng = CesiumMath.toDegrees(cartographic.longitude)
    alt = cartographic.height
    return {
      longitude: lng,
      latitude: lat,
      height: alt
    }
  }


  /**
   * @name bearing 计算两点的角度heading
   * @param startLat 初始点的latitude
   * @param startLng 初始点的longitude
   * @param destLat 第二个点的latitude
   * @param destLng 第二个点的longitude
   * @returns {number}  heading值
   */
  bearing(startLat, startLng, destLat, destLng) {
    // 将角度转换成弧度
    startLat = CesiumMath.toRadians(startLat)
    startLng = CesiumMath.toRadians(startLng)
    destLat = CesiumMath.toRadians(destLat)
    destLng = CesiumMath.toRadians(destLng)

    const y = Math.sin(destLng - startLat) * Math.cos(destLat)
    const x = Math.cos(startLat) * Math.sin(destLat) - Math.sin(startLat) * Math.cos(destLat) * Math.cos(destLng - startLng)
    const brng = Math.atan2(y, x)
    const brngDgr = CesiumMath.toDegrees(brng)
    return (brngDgr + 360) % 360

  }



  /**
   * 更新漫游可配置的内部参数
   * @param options
   */
  updateOptionsParams(options) {
    this.model = options.model || {};
    this.time = options.time;
    this.data = options.data;
    this.multiplier = options.speed || 1 // 用于控制动画速度，1代表实时速度，2代表2倍速等等
    this.isPathShow = defined(options.isPathShow) ? options.isPathShow : true
  }

  /**
   * 监听场景事件
   * @param callback
   */
  addSceneEvent(callback) {
    // 获取在场景更新之后以及场景渲染之前立即引发的事件。事件的订阅者将Scene实例作为第一个参数，将当前时间作为第二个参数参
    if (this.handler instanceof Function) {
      this.handler()
      this.handler = null
    }
    this.handler = window.viewer.scene.preRender.addEventListener(callback);
  }

  /**
   * 根据时间获取漫游的位置
   * @param time
   */
  getRoamingPosition(time) {
    if (this.entity) {
      const position = this.property.getValue(time);
      this.setCameraPosition(position,  { pitch: window.viewer.camera.pitch, range: window.viewer.camera.range })
    }
  }


  /**
   * 读取数据
   * @param url
   * @returns {*}
   */
  loadData(url) {
    let dataUrl;
    if (defined(url) && typeof url === 'string') {
      dataUrl = Resource.fetchJson(url)
    } else {
      dataUrl = url
    }
    return dataUrl
  }

  /**
   * @description 设置路劲是否可见
   * @param {boolean} visible
   * @memberof CesiumRoaming
   */
  setRoamingPathVisibility(visible) {
    if (this.entity) {
      this.entity.path.show = visible
    }
    //更新全局漫游路径是否可见参数
    this.isPathShow = visible
  }

  /**
   * @description 设置漫游模型是否可见
   * @param {boolean} visible
   * @memberof CesiumRoaming
   */
  setRoamingModelVisibility(visible) {
    if (this.entity) {
      this.entity.model.show = visible
    }
  }

  /**
   * @description 漫游的暂停和继续
   * @param {boolean} state false为暂停 true为继续
   */
  pauseOrContinue(state) {
    if (state) {
      //继续播放
      if (!this.handler && this.entity) {
        this.addSceneEvent((time) => {
          this.getRoamingPosition(time)
        })
      }
    } else if (this.handler) {
      //停止监听屏幕绘制事件（停止相机变化）
      this.handler()
      this.handler = null
      //解锁相机视角
      window.viewer.camera.lookAtTransform(Matrix4.IDENTITY)
    }
    window.viewer.clock.shouldAnimate = state
  }
  /**
   * 改变飞行速度
   * @param {number} value 整数类型
   */
  changeRoamingSpeed(value) {
    window.viewer.clock.multiplier = value
  }


  /**
   * 移除漫游和实例
   */
  removeRoaming() {
    if (this.entity) {
      if (this.handler instanceof Function) {
        this.handler()
        this.handler = null
      }
      // 清除实体
      window.viewer.entities.remove(this.entity)
      this.data = null
      // 解锁相机视角
      window.viewer.camera.lookAtTransform(Matrix4.IDENTITY)
      this.entity = null
    }
  }
}